/*
 * Copyright 2006 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.gwt.junit.client;

import com.google.gwt.core.ext.Generator;
import com.google.gwt.junit.rebind.JUnitTestCaseStubGenerator;

import junit.framework.TestCase;
import junit.framework.TestResult;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

/**
 * Acts as a bridge between the JUnit environment and the GWT environment. We
 * hook the run method and stash the TestResult object for later communication
 * between the test runner and the unit test shell that drives the test case
 * inside a hosted browser.
 * 
 * <p>
 * There are two versions of this class. One is used in hosted mode and the
 * other is used in hybrid mode. Implementations are very different between the
 * two modes, making it simpler to have a separate implementation for each.
 * Please see the <code>translatable</code> subpackage for the other
 * implementation.
 * </p>
 */
public abstract class GWTTestCase extends TestCase {

  private static Method runTestMethod;
  private static Object unitTestShell;

  /**
   * Accumulates messages that are printed along with failures (useful in hybrid
   * mode as a substitute for stack traces).
   */
  public void addCheckpoint(String msg) {
    checkPointMessages.add(msg);
  }

  /**
   * Determines whether or not exceptions will be caught by the test fixture.
   */
  public boolean catchExceptions() {
    return true;
  }

  /**
   * Resets the current checkpoint messages.
   */
  public void clearCheckpoints() {
    checkPointMessages.clear();
  }

  /**
   * Gets the current checkpoint messages.
   */
  public String[] getCheckpoints() {
    return (String[]) checkPointMessages.toArray(new String[checkPointMessages
      .size()]);
  }

  /**
   * Tests can only be run in the context of a module. Therefore, the concrete
   * TestCase must provide an implementation for this method.
   * 
   * @return the fully qualified name of the module to use for this test.
   */
  public abstract String getModuleName();

  /**
   * Stashes the <code>TestResult</code> object so that it can be accessed
   * from the unit test shell.
   */
  public void run(TestResult result) {
    testResult = result;
    super.run(result);
  }

  /**
   * Runs the test by delegating to the unit test shell.
   */
  protected void runTest() throws Throwable {
    Throwable caught;
    try {
      if (runTestMethod == null) {
        // Use reflection to avoid build-time dependencies and to provide an
        // opportunity for a useful failure message.
        //
        Class unitTestShellClass;
        try {
          unitTestShellClass = Class
            .forName("com.google.gwt.dev.GWTUnitTestShell");
          Method getUnitTestShellMethod = unitTestShellClass.getDeclaredMethod(
            "getUnitTestShell", new Class[]{Generator.class});
          runTestMethod = unitTestShellClass.getDeclaredMethod("runTest",
            new Class[]{String.class, TestCase.class, TestResult.class});
          Generator generator = new JUnitTestCaseStubGenerator(getClass()
            .getName());
          unitTestShell = getUnitTestShellMethod.invoke(null,
            new Object[]{generator});

          if (unitTestShell == null) {
            throw new RuntimeException(
              "Unable to start required development shell support; see the console for details");
          }
        } catch (ClassNotFoundException e) {
          throw new RuntimeException(
            "Unable to find JUnit integration support; are you running with the proper dev jar?");
        }
      }

      runTestMethod.invoke(unitTestShell, new Object[]{
        getModuleName(), this, testResult});

      clearCheckpoints();

      return;

    } catch (SecurityException e) {
      caught = e;
    } catch (NoSuchMethodException e) {
      caught = e;
    } catch (IllegalArgumentException e) {
      caught = e;
    } catch (IllegalAccessException e) {
      caught = e;
    } catch (InvocationTargetException e) {
      caught = e;
      Throwable nested = caught.getCause();
      if (nested != null) {
        caught = nested;
      }
    }
    throw new RuntimeException(
      "Unable to run unit test due to an unexpected exception", caught);
  }

  /**
   * Not supported in the current implemenation.
   */
  protected final void setUp() throws Exception {
  }

  /**
   * Not supported in the current implementation.
   */
  protected void tearDown() throws Exception {
  }

  /*
   * A list of messages that serve as debugging aids to track down hybrid mode
   * failures.
   */
  private ArrayList checkPointMessages = new ArrayList();

  /*
   * Object that collects the results of this test case execution.
   */
  private TestResult testResult = null;
}
